﻿/*
 ================ 【 MEA 】 泰坦显卡v3 1080Ti型 =================
 将简单的文字指令转换为LCD上的图形，可以是手动输入的指令，也可以是其他编程块的处理结果
 这个程序将大大简化图形LCD界面的使用。

 【使用方法】
 1、安装本代码到一个编程块中，并把LCDNameTag的值设为需要显示的LCD的名字，或者反之把LCD的名字设置为LCDNameTag的值。LCD可以是多个。LCD不需要任何其他手动设置
 2、如果手动使用，请按照【显示语法】规范在本编程块的CustomData中写入要显示的图形，并直接Run本编程块
 3、如果配合其他程序使用，请在其他程序中获取本编程块，并在需要显示图形的时候将图形指令集合成string类型的arguments并以 ThisProgramBlock.TruRun(arguments)的方式触发一次图形界面更新

 【显示模式设置】
 1、多显示模式：在非组合显示模式中，即：PanelHorizontalCount=1并且PanelVerticalCount=1的时候，自动启用多显示模式。这意味着你可以把多个LCD同时命名为LCDNameTag所指定的名字，它们都将获得同样的图形输出
 2、组合显示模式：当开启该模式，多显示模式自动失效。
	你可以使用PanelHorizontalCount（水平LCD数量）和PanelVerticalCount（垂直LCD数量）来将多个LCD合并成一个大屏幕，例如PanelHorizontalCount=2，这时程序会把画布拓展到2个LCD。
	注意，你必须把左上角的LCD名字设置成LCDNameTag的值。程序会根据你设置的水平和垂直LCD数量自动获取这个主LCD相邻的其他LCD。
 3、宽屏模式：当你使用正方形LCD时，请设置为false，当使用宽屏LCD时设置为true。程序会自动兼容两种屏幕，这个模式无论开关都兼容上述两个模式。

 【图像背景设置】
 1、你可以在BackGroundColor_R/G/B中设定你的画布背景颜色的RGB值，它会以纯色表现出来
 2、如果你想使用自定义图像作为背景，例如你使用游戏外的程序获得了一个图片并存入一个LCD的PublicText中，你可以设定BackgroundPictureLCDNameTag的值为你储存背景图的LCD面板中。
	程序会读取这个LCD面板的图像并将它作为画布背景，需要注意的是，你应该使用与显示模式相同尺寸的LCD作为背景作为来源

 【绘图说明】
 注意：本程序使用的画布坐标系是以整个画布的左上角作为座标0点，向右表现为x+，向下表现为y+，你不用担心绘制一个超出画布的图像，程序不会显示它，而且不会运行出错。
 1、本程序绘图流程是在这个编程块的CustomData中写入绘图指令。
 2、所有的图形都包含一个分组string Class和名称string Name属性，所有的操作都是基于单个图形或一组图形或相同名称的图形。图形是整个画布的独立基础元素
 3、当你使用指令创建了一个图形，例如Line:MyFirstLine,0,0,100,0#255,255,255，它会被永久储存在程序中，除非你重新编译这个编程块，或者对它使用Clear(Name,MyFirstLine)指令。
	因此请不要将已经使用过的指令留在CustomData中再次运行，否则你将重复创建多个重叠且名字相同的图形，这会造成像素堆积，程序会由于过于复杂而停止。
 4*、如果你是C#使用者，请在主程序Figuer Area Start和Figuer Area End之间通过 AllFigure.Add(new Figure(DrawFunction(), char color)) 的方式创建图形，同时你也可以使用class Figure里的指令对其进行操作。RGB颜色转换函数是GetColorFromRGB()
 5、本程序会将已经存在的图形Echo到本编程块的右下角，包含图形组、名字、中心座标。便于玩家识别
 
【绘图语法】
本程序使用强规则语法，更好的兼容其他编程块的调用。同时可使用额外的语法解释器将指令从弱规则转为标准规则，便于用户直接指令绘图
1、所有语法以行为基本单位，在编程块中使用string "\n" 分割不同的指令，在手动输入时直接使用回车键。
2、所有指令之间不可存在多余的空格、其他符号等，否则编译出错
3、指令格式如下：
    首先我们了解一下构图的原理，所有的图形都是由一个我们自己定义的实体组成的，这个实体有如下参数，string Class(组名)，string Name(名字)，List<Vector2I> Pixs(包含的像素的座标集合)，List<char> Color(每个像素对应的颜色)，boo OnOff(是否显示)
    你要画一个图形，首先要创建一个图形，下方我们提供了一个指令用于创建图形，你只需要声明图形的种类（线、圆、矩形、三角）、组名，名字，并提供相应的参数（起始点座标、半径、长度、宽度等）
    因此你需要使用指令空间的[Draw]指令绘制一个图形，并在这条指令的命名空间写上这个图形是什么，并为它起一个编组名字和它自己的名字{Line,Class_Name,Figure_Name}，并在参数空间写上起始点座标，终点座标(start_x,start_y,end_x,end_y)
    并用一个特殊的颜色空间#255,255,255#来声明它的颜色(这个颜色是一个RGB色值，你可以在LCD面板的颜色条上找到你想要的任何颜色)。你将在画布上获得这个图形。
    我们来把上面的各个空间结合起来，这条指令应该是这样的：[Draw]{Line,ClassName,Name}(0,0,100,100)#255,255,255#
    注意，程序获取这条指令的时候，只会取被[]{}()##符号包裹的内容，它们的顺序不会影响程序的执行，你可以在包裹区域外写上任何你喜欢的内容，这都不会影响程序的执行
    但是！有些指令是不需要参数空间的，例如清空画布的指令[Clear]{}()，它不需要参数空间和命名空间，但你必须把参数空间和命名空间的包裹符号写出来，否则程序会编译出错
	
    下面我们正式进入指令的学习：
    首先，第一行你可以写上任何内容，程序会跳过第一行，不对它进行编译
    其余的每一行表示一条指令，我们重申一下每条指令必须包括3个组成部分，用[]包裹的指令空间、用{}包裹的命名空间，用()包裹的参数空间。就算这个空间内没有需要给出的具体内容，也必须写上包裹符号
    所以，你也不可以在整个指令的末尾留下一个空的空行，程序会认为它是一条指令，但它没有任何空间包裹符号，因此程序会错误
    其中以[]包裹的指令空间优先级最高，它告诉程序你的指令将要执行什么动作，{}包裹的命名空间会告诉指令你将要对哪个图形执行这个动作，()包裹的参数空间会告诉程序一些必要的参数
    下面举个例子：
       在编程块的CustomData中写上：
	   
	   This is My First Picture~
       [Draw]{Circle,CL,MyFirstPicture}(50,50,30,false,0.8)#255,255,255#  
       [Move]{Class,CL}(30,0)
	   
       第二行表示你创建了一个圆，它的起始座标是x50,y50，半径是30，空心(true表示实心)，边框精度是0.8，颜色是白色
       第三行表示你按照图形的组名Class搜索组名为CL的图形，并将它平移了x=30的距离。
	   
	   注意，如果对编程块运行了两次，编程块就接收到了两次同样的指令，所以会生成两个组名为CL，名字为MyFirstPicture的圆，并且对它们做了同样的平移。
       这两个圆是刚好重合的，你并不能在画布上分辨它们。但你可以开启自定义变量的IsShowFigureInEcho选项，从编程块右下角输出的数据中看到，确实有两个一样的图形。

       所以，当你运行完一条指令，需要输入新的指令时，一定要将之前的指令删除。
       !!! * 如果你不想删除，没关系，在这行指令的最左边加上双斜杠//，类似C#语言中的注释，同样可以让程序跳过对这行指令的编译
       就像这样：
       //[Move]{Class,CL}(30,0)
	   
    指令包括（所有数值除非特殊指明，否则均默认是整形int）：
    绘图：[Draw]{图形,图形组名,图形名称}(value1,value2,...)#R,G,B#     ！注意，在这个指令中你必须额外写入一个##包裹的颜色空间
            其中图形包括：
            直线：[Draw]{Line,Class,Name}(start_x,start_y,end_x,end_y)#R,G,B#
            圆：  [Draw]{Circle,Class,Name}(center_x,center_y, r ,issolid,ratio)#R,G,B#    其中issolid表示是否是实心圆(true,false)，当是实心圆时，ratio表示边框精度，当是空心圆环时，ratio(double)表示边框绘制精度。
            矩形：[Draw]{Rectangle,Class,Name}(start_x,start_y,width,height)#R,G,B#
                  [Draw]{Rectangle,Class,Name}(start_x,start_y,width,height,thickness)#R,G,B#   其中thickness如果不写，表示实心矩形，写了表示空心矩形，这个参数是空心矩形的边框像素宽度
            三角：[Draw]{Triangle,Class,Name}(start_x,start_y,width,height)#R,G,B#
                  [Draw]{Triangle,Class,Name}(start_x,start_y,width,height,thickness,needbase)#R,G,B#  其中thikness如果不写表示实心三角，写了表示空心三角，这个参数是空心三角边框厚度，needbase表示空心三角是否绘制底边
            字符：[Draw]{String,Class,Name}(content,start_x,end_x)#R,G,B#   其中stat_x和stat_y表示字符左上角起始座标，注意，你不能在字符串内容中使用符号[,]
          多项式：[Draw]{Polynomial,Class,Name}(<exp1,rat1><exp2,rat2>,start_x,end_x)#R,G,B# 其中参数空间第一个必须是多项式表达式，由多个<exp,rat>组成，exp表示这一项x的指数，rat表示这一项x的系数
    平移：[Move]{组/名称, 组/名称值}(值1,值2)
          例如：移动一个组 [Move]{Class,ClassName}(x,y)  表示按组名匹配，组名为ClassName的图形都被移动(x,y)个像素
    旋转：[Rotate]{Class/Name, ClassValue/NameValue}(Angle)    Angle表示旋转角度，单位是角度
           [Rotate]{Class/Name, ClassValue/NameValue}(Angle,x,y)     如果你额外传入了两个参数x和y，程序会把这个图形以座标xy进行旋转，否则默认按图形自己的几何中心旋转
    删除：[Delete]{Class/Name, ClassValue/NameValue}()     这是一个不需要参数空间的指令，但你仍然要写上参数包裹符号()
    清空画布：  [Clear]{}()   这个指令不需要命名空间和参数空间，它会清空所有图形
    参考线： [ReferenceLine]{}(distance, R, G, B)   你需要在参数空间写入参考线的间隔、颜色值R,G,B，程序会生成参考系。*慎用，如果参考线密度太大可能导致程序复杂终止

	
【程序版本】
当前版本v3.0
我们将收集使用反馈并在后续版本中加入更多图形库和图形变换指令
*/

// ---------------------------------- 自定义参数开始 -------------------------------------------

//方块设置
string LCDNameTag = "LCD";			//需要显示的LCD面板名字，可以是多个，名字必须完全匹配，区分大小写。你可以在多个地方安置屏幕，程序会一同控制它们。注意：这和组合屏幕使用是两个概念，组合屏幕是指将多个屏幕放在一起形成一个大屏幕

//显示模式设置
bool IsShowFigureInEcho = true;		//是否将存在的图形显示到Echo中
bool IsDebugOn = true;				//是否开启指令错误汇报

bool IsRunTimeMode = false;			//是否开启实时模式。在实时模式下，你需要用一个定时块实时触发本编程块。编程块会实时接受指令，但只按设定的频率绘图，避免造成卡死
double RunTimeRate = 30;				//实时模式下，屏幕每秒刷新图像的次数，范围0到60。越大屏幕刷新越快，会导致卡顿，可以是小数。

double ResolutionRatio = 0.1; 		//分辨率，相当于LCD面板设置的字体，范围0.1到10，越小像素越细，可显示像素越多，但会影响性能
bool IsWideScreen = false;			//是否是宽屏LCD，true是宽屏，false是方形
int PanelHorizontalCount = 2;		//组合屏模式，屏幕水平数量，仅左上角的主屏幕按名字获取，其他屏幕可随意命名
int PanelVerticalCount = 2;			//组合屏模式，屏幕垂直数量

//背景设置
int BackGroundColor_R = 0;	//背景色，使用RGB色值，0-255
int BackGroundColor_G = 0;
int BackGroundColor_B = 0;
string BackgroundPictureLCDNameTag = ""; //存放背景图片的LCD名字，你需要在游戏外把图片转换，并于这个LCD的PublicText中，不使用就留空。*这个模式不支持组合屏幕

// ---------------------------------- 自定义参数结束 -------------------------------------------

List<Figure> AllFigure = new List<Figure>(); //全部图形像素点集合.

int PixWidth; //根据分辨率计算出的屏幕宽
int PixHeight; //根据分辨率计算出的屏幕高

List<IMyTextPanel> MainPanel;
IMyTextPanel[][] LayoutPanels;

bool IsPanelInit;
string DebugString;
int t;
void Main(string arguments)
{
	//初始化获取屏幕和组合屏幕
	if(!IsPanelInit) InitPanels();

	//处理屏幕画布尺寸
	PixHeight = (int)Math.Round(17.7*PanelVerticalCount/ResolutionRatio,0);
	PixWidth = (int)Math.Round(17.7*PanelHorizontalCount/ResolutionRatio,0);
	if(IsWideScreen) PixWidth *= 2;

	//绘图区域开始 Figurer Area Start
	/* Example:
	if(arguments == "a")
	{
		AllFigure.Add(new Figure("Circle",DrawCircle(50,new Vector2I(380,100),false,0.8), '\uE2FF'));
		AllFigure.Add(new Figure("Circle",DrawTextLine("Yes I Can",10,10), '\uE2FF'));
		AllFigure.Add(new Figure("Circle",DrawCircle(50,new Vector2I(280,80),false,0.8), '\uE2FF'));
		AllFigure.Add(new Figure("Circle",DrawCircle(50,new Vector2I(320,60),false,0.8), '\uE2FF'));
	}
	if(arguments == "b")
	{
		AllFigure[0].Rotate(10);
		Echo(AllFigure[0].Pixs.Count.ToString());
	}
	if(arguments == "c")
	{
		DrawReferenceLine(40,255,255,255);
	}
	*/
	
	//解析指令
	if(arguments != "") AnalysisCommands(arguments.Split('\n'));
	else AnalysisCommands(Me.CustomData.Split('\n'));
	
	//绘图区域结束 Figurer Area End
	
	//显示区域
	t ++ ; //时钟
	if(IsRunTimeMode) {if(t%Math.Round(60/RunTimeRate,0) == 0) ShowOnLCD(AllFigure);} 
	else{ShowOnLCD(AllFigure);}

	//参数输出
	Echo("PixWidth: " + PixWidth.ToString());
	Echo("PixHeight: " + PixHeight.ToString());
	if(IsShowFigureInEcho)
	{
		for(int i = 0; i < AllFigure.Count; i ++)
		{
			Echo(AllFigure[i].Class + ": " + AllFigure[i].Name + ": " + AllFigure[i].GetCenterPoint().ToString());
		}
	}
	if(IsDebugOn) Echo(DebugString); DebugString = "";
}

//初始化获取主LCD和组合LCD，根据设置的水平屏幕个数和垂直屏幕个数自动判断是否需要组合LCD
void InitPanels()
{
	//初始化主屏
	MainPanel = new List<IMyTextPanel>();
	GridTerminalSystem.GetBlocksOfType(MainPanel, b => b.CustomName == LCDNameTag);

	for(int i = 0; i < MainPanel.Count; i ++)
	{
		MainPanel[i].SetValue("Font",1147350002L);
		MainPanel[i].SetValue("FontSize",(float)ResolutionRatio);
		MainPanel[i].ShowTextureOnScreen();
		MainPanel[i].ShowPublicTextOnScreen();
	}

	//初始化组合屏
	Vector3I pnLeft = -Base6Directions.GetIntVector(MainPanel[0].Orientation.Left);
	Vector3I pnDown = -Base6Directions.GetIntVector(MainPanel[0].Orientation.Up);
	Vector3I pnSize = MainPanel[0].Max - MainPanel[0].Min + new Vector3I(1, 1, 1);
	pnLeft = new Vector3I(pnLeft.X * pnSize.X, pnLeft.Y * pnSize.Y, pnLeft.Z * pnSize.Z);
	pnDown = new Vector3I(pnDown.X * pnSize.X, pnDown.Y * pnSize.Y, pnDown.Z * pnSize.Z);

	int maxArea = 0;
	int mkX = 0;
	int mkY = 0;
	LayoutPanels = new IMyTextPanel[PanelVerticalCount][];
	for (int y = 0; y < PanelVerticalCount; y++)
	{
		LayoutPanels[y] = new IMyTextPanel[PanelHorizontalCount];
		for (int x = 0; x < PanelHorizontalCount; x++)
		{
			IMySlimBlock slimBlock = MainPanel[0].CubeGrid.GetCubeBlock(MainPanel[0].Position + (x * pnLeft) + (y * pnDown));

			IMyTextPanel panel = (slimBlock == null ? null : slimBlock.FatBlock as IMyTextPanel);
			if (panel == null)
			{
				if (x == 0)
				{
					PanelVerticalCount = y;
				}
				break;
			}

			int area = (x + 1) * (y + 1);
			if (area > maxArea)
			{
				maxArea = area;
				mkX = x;
				mkY = y;
			}

			LayoutPanels[y][x] = panel;

			LayoutPanels[y][x].SetValue("FontSize",(float)ResolutionRatio);
			LayoutPanels[y][x].SetValue("Font",1147350002L);

			LayoutPanels[y][x].ShowTextureOnScreen();
			LayoutPanels[y][x].ShowPublicTextOnScreen();
		}
	}

	IsPanelInit = true;
}

//显示所有图形到LCD函数，包括了创建背景画布和图形覆盖的动作
void ShowOnLCD(List<Figure> Figures)
{
	//生成背景画布：默认背景色或图像
	char[][] All_Pix = GetBackground(BackgroundPictureLCDNameTag);

	//绘制所有图形到整个画布
	for(int i = 0; i < Figures.Count; i ++)
	{
		if(Figures[i].OnOff)
		{
			for(int j = 0; j < Figures[i].Pixs.Count; j ++)
			{
				if(Figures[i].Pixs[j].X >= 0 && Figures[i].Pixs[j].X < PixWidth && Figures[i].Pixs[j].Y >= 0 && Figures[i].Pixs[j].Y < PixHeight)
				{
					All_Pix[Figures[i].Pixs[j].Y][Figures[i].Pixs[j].X] = Figures[i].Color[j];
				}
			}
		}
	}

	//多LCD同时显示，非组合模式
	if(PanelVerticalCount == 1 && PanelHorizontalCount == 1)
	{
		string OutPutString = "";
		for(int y = 0; y < PixHeight; y ++)
		{
			OutPutString += new string(All_Pix[y]) + "\n";
		}
		for(int i = 0; i < MainPanel.Count; i ++)
		{
			MainPanel[i].WritePublicText(OutPutString);
		}
	}
	else //组合显示模式，此模式下多LCD显示被关闭
	{
		//分割画布到各个组合LCD上
		string[][] All_String = new string[PanelVerticalCount][];
		for(int i = 0; i < PanelVerticalCount; i ++)
		{
			All_String[i] = new string[PanelHorizontalCount];
		}

		//连接画布的每个像素形成整体
		for(int y_panel = 0; y_panel < PanelVerticalCount; y_panel ++)
		{
			for(int x_panel = 0; x_panel < PanelHorizontalCount; x_panel ++)
			{
				for(int y_pix = y_panel*PixHeight/PanelVerticalCount; y_pix < (y_panel+1)*PixHeight/PanelVerticalCount; y_pix ++)
				{
					char[] ThisPanelThisLine = new char[PixWidth/PanelHorizontalCount];
					Array.ConstrainedCopy(All_Pix[y_pix], x_panel*PixWidth/PanelHorizontalCount, ThisPanelThisLine, 0, PixWidth/PanelHorizontalCount);
					All_String[y_panel][x_panel] += new string(ThisPanelThisLine) + "\n";
				}
				if(LayoutPanels[y_panel][x_panel] != null) LayoutPanels[y_panel][x_panel].WritePublicText(All_String[y_panel][x_panel]);
			}
		}
	}

}

//获取背景图层或生成默认背景色的画布
char[][] GetBackground(string LCDName)//从指定名字LCD上获取图像背景，没有则默认背景色背景
{
	IMyTextPanel LCD = GridTerminalSystem.GetBlockWithName(LCDName) as IMyTextPanel;
	if(LCD == null)
	{
		char[][] Result = new char[PixHeight][];
		for(int y = 0; y < PixHeight; y ++)
		{
			Result[y] = new string(GetColorFromRGB(BackGroundColor_R,BackGroundColor_G,BackGroundColor_B), PixWidth).ToCharArray();
		}
		return Result;
	}
	else
	{
		string[] line_info = LCD.GetPublicText().Split('\n');
		char[][] Result = new char[line_info.Count()][];
		for (int y = 0; y < line_info.Count(); y++)
		{
			Result[y] = line_info[y].ToCharArray();
		}
		return Result;
	}
}

//解析CustomData成为指令
void AnalysisCommands(string[] cmds)
{
	//[Action]{Class,Name}(value,value,value)#R,G,B#
	for(int i = 1; i < cmds.Length; i ++)
	{
		string this_cmd = cmds[i];
		if(this_cmd.StartsWith("//")) continue; //注释该行指令
		
		string cmd_Action = this_cmd.Split('[')[1].Split(']')[0]; //指令空间
		string[] cmd_NameSpace = this_cmd.Split('{')[1].Split('}')[0].Split(','); //名称空间
		string[] cmd_Value = this_cmd.Split('(')[1].Split(')')[0].Split(','); //值空间
		bool havedothis = false; //指示这条指令是否被执行了
		switch(cmd_Action)
		{
			case ("Draw"):
			int CL_R = int.Parse(this_cmd.Split('#')[1].Split(',')[0]);
			int CL_G = int.Parse(this_cmd.Split('#')[1].Split(',')[1]);
			int CL_B = int.Parse(this_cmd.Split('#')[1].Split(',')[2]);
			switch(cmd_NameSpace[0])
			{
				case ("Line"):
				if(cmd_Value.Length == 4) 
				{
					//[Draw]{Line,Class,Name}(start_x,start_y,end_x,end_y)#R,G,B#
					Vector2I startpoint = new Vector2I(int.Parse(cmd_Value[0]), int.Parse(cmd_Value[1]));
					Vector2I endpoint = new Vector2I(int.Parse(cmd_Value[2]), int.Parse(cmd_Value[3]));
					AllFigure.Add(new Figure(cmd_NameSpace[1], cmd_NameSpace[2], DrawLine(startpoint,endpoint), GetColorFromRGB(CL_R, CL_G, CL_B)));
					havedothis = true;
				}
				else DebugString += "Error Line:" + (i+1).ToString() + "\n";
				break;
				case ("Circle"):
				if(cmd_Value.Length == 5)
				{
					//[Draw]{Circle,Class,Name}(center_x,center_y, r ,issolid,ratio)#R,G,B#
					Vector2I centerpoint = new Vector2I(int.Parse(cmd_Value[0]), int.Parse(cmd_Value[1]));
					int circle_r = int.Parse(cmd_Value[2]);
					bool circle_solid = (cmd_Value[3] == "true");
					double circle_ratio = double.Parse(cmd_Value[4]);
					AllFigure.Add(new Figure(cmd_NameSpace[1], cmd_NameSpace[2], DrawCircle(circle_r, centerpoint, circle_solid, circle_ratio), GetColorFromRGB(CL_R, CL_G, CL_B)));
					havedothis = true;
				}
				else DebugString += "Error Line:" + (i+1).ToString() + "\n";
				break;
				case ("Rectangle"):
				if(cmd_Value.Length == 4)
				{
					//[Draw]{Rectangle,Class,Name}(start_x,start_y,width,height)#R,G,B#
					Vector2I startpoint = new Vector2I(int.Parse(cmd_Value[0]), int.Parse(cmd_Value[1]));
					int width = int.Parse(cmd_Value[2]);
					int height = int.Parse(cmd_Value[3]);
					AllFigure.Add(new Figure(cmd_NameSpace[1], cmd_NameSpace[2], DrawRectangle(startpoint, width, height), GetColorFromRGB(CL_R, CL_G, CL_B)));
					havedothis = true;
				}
				else if(cmd_Value.Length == 5)
				{
					//[Draw]{Rectangle,Class,Name}(start_x,start_y,width,height,thickness)#R,G,B#
					Vector2I startpoint = new Vector2I(int.Parse(cmd_Value[0]), int.Parse(cmd_Value[1]));
					int width = int.Parse(cmd_Value[2]);
					int height = int.Parse(cmd_Value[3]);
					int thickness = int.Parse(cmd_Value[4]);
					AllFigure.Add(new Figure(cmd_NameSpace[1], cmd_NameSpace[2], DrawRectangle(startpoint, width, height, thickness), GetColorFromRGB(CL_R, CL_G, CL_B)));
					havedothis = true;
				}
				else DebugString += "Error Line:" + (i+1).ToString() + "\n";
				break;
				case ("Triangle"):
				if(cmd_Value.Length == 4)
				{
					//[Draw]{Triangle,Class,Name}(start_x,start_y,width,height)#R,G,B#
					Vector2I startpoint = new Vector2I(int.Parse(cmd_Value[0]), int.Parse(cmd_Value[1]));
					int width = int.Parse(cmd_Value[2]);
					int height = int.Parse(cmd_Value[3]);
					AllFigure.Add(new Figure(cmd_NameSpace[1], cmd_NameSpace[2], DrawTriangle(startpoint, width, height), GetColorFromRGB(CL_R, CL_G, CL_B)));
					havedothis = true;
				}
				else if(cmd_Value.Length == 6)
				{
					//[Draw]{Triangle,Class,Name}(start_x,start_y,width,height,thickness,needbase)#R,G,B#
					Vector2I startpoint = new Vector2I(int.Parse(cmd_Value[0]), int.Parse(cmd_Value[1]));
					int width = int.Parse(cmd_Value[2]);
					int height = int.Parse(cmd_Value[3]);
					int thickness = int.Parse(cmd_Value[4]);
					bool needbase = (cmd_Value[5] == "true");
					AllFigure.Add(new Figure(cmd_NameSpace[1], cmd_NameSpace[2], DrawTriangle(startpoint, width, height, thickness, needbase), GetColorFromRGB(CL_R, CL_G, CL_B)));
					havedothis = true;
				}
				break;
				case ("String"):
				if(cmd_Value.Length == 3)
				{
					//[Draw]{String,Class,Name}(content,start_x,end_x)#R,G,B#
					string content = cmd_Value[0];
					int start_x = int.Parse(cmd_Value[1]);
					int start_y = int.Parse(cmd_Value[2]);
					AllFigure.Add(new Figure(cmd_NameSpace[1], cmd_NameSpace[2], DrawTextLine(content, start_x, start_y), GetColorFromRGB(CL_R, CL_G, CL_B)));
					havedothis = true;
				}
				else DebugString += "Error Line:" + (i+1).ToString() + "\n";
				break;
				case ("Polynomial"):
				if(cmd_Value.Length > 2)
				{
					//[Draw]{Polynomial,Class,Name}(<exp1,rat1><exp2,rat2>,start_x,end_x)#R,G,B#
					int n_number = cmd_Value[0].Split('<').Length - 1;
					double[][] input = new double[n_number][];
					for(int j = 0; j < n_number; j ++)
					{
						double exp = double.Parse(cmd_Value[0].Split('<')[j+1].Split('>')[0].Split(';')[0]);
						double rat = double.Parse(cmd_Value[0].Split('<')[j+1].Split('>')[0].Split(';')[1]);
						input[j] = new double[]{exp,rat};
					}
					int start_x = int.Parse(cmd_Value[1]);
					int end_x = int.Parse(cmd_Value[2]);
					AllFigure.Add(new Figure(cmd_NameSpace[1], cmd_NameSpace[2], DrawPolynomial(input, start_x, end_x), GetColorFromRGB(CL_R, CL_G, CL_B)));
					havedothis = true;
				}
				else DebugString += "Error Line:" + (i+1).ToString() + "\n";
				break;
			}
			break;
			case ("Move"):
			//[Move]{Class,FigureClass_A}(x,y)
			//[Move]{Name,FigureName_A}(x,y)
			int x = int.Parse(cmd_Value[0]);
			int y = int.Parse(cmd_Value[1]);
			for(int j = 0; j < AllFigure.Count; j ++)
			{
				if(cmd_NameSpace[0] == "Class" && AllFigure[j].Class == cmd_NameSpace[1])
				{
					AllFigure[j].Move(x,y);
					havedothis = true;
				}
				else if(cmd_NameSpace[0] == "Name" && AllFigure[j].Name == cmd_NameSpace[1])
				{
					AllFigure[j].Move(x,y);
					havedothis = true;
				}
			}
			break;
			case ("Rotate"):
			//[Rotate]{Class,FigureClass_A}(angle) or (angle,center_x,center_y)
			//[Rotate]{Class,FigureName_A}{angle} or (angle,center_x,center_y)
			for(int j = 0; j < AllFigure.Count; j ++)
			{
				if(cmd_NameSpace[0] == "Class" && AllFigure[j].Class == cmd_NameSpace[1])
				{
					if(cmd_Value.Length == 1)
					{AllFigure[j].Rotate(int.Parse(cmd_Value[0]));havedothis = true;}
					else if(cmd_Value.Length == 3)
					{AllFigure[j].Rotate(int.Parse(cmd_Value[0]) , new Vector2I(int.Parse(cmd_Value[1]), int.Parse(cmd_Value[2])));havedothis = true;}
				}
				else if(cmd_NameSpace[0] == "Name" && AllFigure[j].Name == cmd_NameSpace[1])
				{
					if(cmd_Value.Length == 1)
					{AllFigure[j].Rotate(int.Parse(cmd_Value[0]));havedothis = true;}
					else if(cmd_Value.Length == 3)
					{AllFigure[j].Rotate(int.Parse(cmd_Value[0]) , new Vector2I(int.Parse(cmd_Value[1]), int.Parse(cmd_Value[2])));havedothis = true;}
				}
			}
			break;
			case ("Clear"):
			//[Clear]{}()
			AllFigure = new List<Figure>();
			havedothis = true;
			break;
			case ("Show"):
			for(int j = 0; j < AllFigure.Count; j ++)
			{
				if(cmd_NameSpace[0] == "Class" && AllFigure[j].Class == cmd_NameSpace[1])
				{
					AllFigure[j].OnOff = (cmd_Value[0] == "true");
					havedothis = true;
				}
				else if(cmd_NameSpace[0] == "Name" && AllFigure[j].Name == cmd_NameSpace[1])
				{
					AllFigure[j].OnOff = (cmd_Value[0] == "true");
					havedothis = true;
				}
			}
			break;
			case ("Delete"):
			for(int j = 0; j < AllFigure.Count; j ++)
			{
				if(cmd_NameSpace[0] == "Class" && AllFigure[j].Class == cmd_NameSpace[1])
				{
					AllFigure.Remove(AllFigure[j]);
					havedothis = true;
				}
				else if(cmd_NameSpace[0] == "Name" && AllFigure[j].Name == cmd_NameSpace[1])
				{
					AllFigure.Remove(AllFigure[j]);
					havedothis = true;
				}
			}
			break;
			case ("ReferenceLine"):
			DrawReferenceLine(int.Parse(cmd_Value[0]), int.Parse(cmd_Value[1]), int.Parse(cmd_Value[2]), int.Parse(cmd_Value[3]));
			havedothis = true;
			break;
		}
		if(!havedothis) DebugString += "Error Line:" + (i+1).ToString() + "\n";
		
	}
}

//绘制参考网格线
void DrawReferenceLine(int distance, int C_R, int C_G, int C_B)
{
	char color = GetColorFromRGB(C_R,C_G,C_B);
	for(int i = distance; i <= (PixHeight > PixWidth ? PixHeight : PixWidth); i += distance)
	{
		if(i <= PixHeight) AllFigure.Add(new Figure("RF_Line", ("RF_Line_Y_" + i.ToString()), DrawLine(new Vector2I(0,i), new Vector2I(PixWidth,i)), color));
		if(i <= PixWidth)  AllFigure.Add(new Figure("RF_Line", ("RF_Line_X_" + i.ToString()), DrawLine(new Vector2I(i,0), new Vector2I(i,PixHeight)), color));
	}
}

// ============================================== 图像类函数 =========================================
// 图形类
public class Figure
{
	public string Class {get; set;} //所属集合
	public string Name; //名字
	public List<Vector2I> Pixs; //像素点集合
	public List<char> Color; //像素点对应颜色
	public bool OnOff; //显示开关
	public Figure()
	{
		Class = "";
		Name = "";
		Pixs = new List<Vector2I>();
		Color = new List<char>();
		OnOff = false;
	}
	public Figure(string InputName, List<Vector2I> InputPixs, char InputColor)
	{
		Class = "Undefined";
		Name = InputName;
		Pixs = InputPixs;
		Color = new List<char>();
		for(int i = 0; i < Pixs.Count; i ++)
		{
			Color.Add(InputColor);
		}
		OnOff = true;
	}
	public Figure(string InputClass, string InputName, List<Vector2I> InputPixs, char InputColor)
	{
		Class = InputClass;
		Name = InputName;
		Pixs = InputPixs;
		Color = new List<char>();
		for(int i = 0; i < Pixs.Count; i ++)
		{
			Color.Add(InputColor);
		}
		OnOff = true;
	}

	public void Add(List<Vector2I> PixPoints, char InputColor)
	{
		this.Pixs.AddList(PixPoints);
		for(int i = 0; i < PixPoints.Count; i ++)
		{
			Color.Add(InputColor);
		}
	}
	public void Clear()
	{
		this.Pixs = new List<Vector2I>();
		this.Color = new List<char>();
	}

	//平移
	public void Move(int x, int y)
	{
		for(int i = 0; i < this.Pixs.Count; i ++)
		{
			Vector2I aa = this.Pixs[i];
			this.Pixs[i] = aa + new Vector2I(x,y);
		}
	}

	//旋转
	public void Rotate(int Angle)
	{
		this.Rotate(Angle, this.GetCenterPoint());
	}
	public void Rotate(int Angle, Vector2I CenterPoint)
	{
		double RadianAngle = Angle*Math.PI/180; //转弧度
		List<Vector2I> returnPoint = new List<Vector2I>();
		//座标旋转公式
		for(int i = 0; i < this.Pixs.Count; i ++)
		{
			int rt_x = (int)Math.Round(((this.Pixs[i].X - CenterPoint.X)*Math.Cos(RadianAngle)) - ((this.Pixs[i].Y - CenterPoint.Y)*Math.Sin(RadianAngle)) + CenterPoint.X, 0);
			int rt_y = (int)Math.Round(((this.Pixs[i].X - CenterPoint.X)*Math.Sin(RadianAngle)) + ((this.Pixs[i].Y - CenterPoint.Y)*Math.Cos(RadianAngle)) + CenterPoint.Y, 0);
			returnPoint.Add(new Vector2I(rt_x, rt_y));
		}
		this.Pixs = returnPoint;
	}
	
	//放大
	public void Inflate(int Ratio)
	{
		if(Ratio >= 1)
		{
			for(int i = 0; i < Ratio; i ++)
			{
				
			}
		}
	}
	
	//获取宽度
	public int GetWidth()
	{
		int min_x = int.MaxValue;
		int max_x = int.MinValue;
		for(int i = 0; i < this.Pixs.Count; i ++)
		{
			if(this.Pixs[i].X <= min_x)
			{
				min_x = this.Pixs[i].X;
			}
			if(this.Pixs[i].X >= max_x)
			{
				max_x = this.Pixs[i].X;
			}
		}
		return max_x - min_x;
	}
	
	//获取高度
	public int GetHeight()
	{
		int min_y = int.MaxValue;
		int max_y = int.MinValue;
		for(int i = 0; i < this.Pixs.Count; i ++)
		{
			if(this.Pixs[i].Y <= min_y)
			{
				min_y = this.Pixs[i].Y;
			}
			if(this.Pixs[i].Y >= max_y)
			{
				max_y = this.Pixs[i].Y;
			}
		}
		return max_y - min_y;
	}

	//获取中心点
	public Vector2I GetCenterPoint()
	{
		int min_x = int.MaxValue;
		int max_x = int.MinValue;
		int min_y = int.MaxValue;
		int max_y = int.MinValue;
		for(int i = 0; i < this.Pixs.Count; i ++)
		{
			if(this.Pixs[i].X <= min_x)
			{
				min_x = this.Pixs[i].X;
			}
			if(this.Pixs[i].X >= max_x)
			{
				max_x = this.Pixs[i].X;
			}
			if(this.Pixs[i].Y <= min_y)
			{
				min_y = this.Pixs[i].Y;
			}
			if(this.Pixs[i].Y >= max_y)
			{
				max_y = this.Pixs[i].Y;
			}
		}
		return new Vector2I((int)Math.Round(0.5*(max_x - min_x) + min_x), (int)Math.Round(0.5*(max_y - min_y) + min_y));
	}
}


// --------- 直线函数 ------------
// 起始点座标，终点座标
List<Vector2I> DrawLine(Vector2I StartPoint, Vector2I EndPoint)
{
	List<Vector2I> returnPoint = new List<Vector2I>();
	int X_dis = Math.Abs(StartPoint.X-EndPoint.X);
	int Y_dis = Math.Abs(StartPoint.Y-EndPoint.Y);
	if(X_dis == 0 && Y_dis == 0)
	{
		returnPoint.Add(new Vector2I(StartPoint.X,StartPoint.Y));
		return returnPoint;
	}
	else if(X_dis == 0)//竖线
	{
		for(int i = (StartPoint.Y < EndPoint.Y ? StartPoint.Y : EndPoint.Y); i <= (StartPoint.Y > EndPoint.Y ? StartPoint.Y : EndPoint.Y); i ++)
		{
			returnPoint.Add(new Vector2I(StartPoint.X,i));
		}
	}
	else if(Y_dis == 0)//横线
	{
		for(int i = (StartPoint.X < EndPoint.X ? StartPoint.X : EndPoint.X); i <= (StartPoint.X > EndPoint.X ? StartPoint.X : EndPoint.X); i ++)
		{
			returnPoint.Add(new Vector2I(i,StartPoint.Y));
		}
	}
	else
	{
		double k = (double)(EndPoint.Y - StartPoint.Y)/(double)(EndPoint.X - StartPoint.X);
		double b = (double)(EndPoint.X*StartPoint.Y - StartPoint.X*EndPoint.Y)/(double)(EndPoint.X - StartPoint.X);
		if(X_dis > Y_dis)
		{
			for(int i = (StartPoint.X < EndPoint.X ? StartPoint.X : EndPoint.X); i <= (StartPoint.X > EndPoint.X ? StartPoint.X : EndPoint.X); i ++)
			{
				double y = k*i + b;
				returnPoint.Add(new Vector2I(i,(int)Math.Round(y,0)));
			}
		}
		else
		{
			for(int i = (StartPoint.Y < EndPoint.Y ? StartPoint.Y : EndPoint.Y); i <= (StartPoint.Y > EndPoint.Y ? StartPoint.Y : EndPoint.Y); i ++)
			{
				double x = (i - b)/k;
				returnPoint.Add(new Vector2I((int)Math.Round(x,0),i));
			}
		}
	}
	return returnPoint;
}

// ---------- 圆函数 ------------
// 半径，圆心座标，参数，是否实心
// 这里的参数是double类型的，可以是小数，它有两种用途：
// 当绘制实心圆时，该参数表示这个圆的圆角的半径，取值范围0到圆半径。例如圆半径为10，参数为10得到正圆，参数为5得到圆角矩形，参数为0得到矩形
// 当绘制空心圆环时，该参数表示圆环厚度，取值范围0到圆半径。例如圆半径为10，参数为0得到空图形，参数为0.5得到0.5厚度的圆环，当参数超过1并趋近圆半径，内边框依然是圆，外边框逐步从圆变为圆角矩形最后变为矩形
// 【注意】半径不宜太大

// 这里有个经验，之前把for循环中写为 int x = R-Point.X; x <= R+Point.X，结果还是一样，是由于圆的对称性，但那加大了循环次数
// 核心算法是那个判断 if(IsSolid && Math.Pow(x,2) + Math.Pow(y,2) - Math.Pow(R,2) <= Accuracy)，必须写为平方差开根号再与R比较，如果直接平方和与R平方比较，效果很差，得到矩形
List<Vector2I> DrawCircle(int R, Vector2I Point, bool IsSolid, double Accuracy)
{
	List<Vector2I> returnPoint = new List<Vector2I>();
	for(int x = 0; x <= R; x ++)
	{
		for(int y = 0; y <= R; y++)
		{
			if(IsSolid && Math.Pow(Math.Pow(x,2) + Math.Pow(y,2), 0.5) - R <= (Accuracy == 0 ? R : 1/Accuracy))
			{
				returnPoint.Add(new Vector2I(Point.X+x, Point.Y+y));
				returnPoint.Add(new Vector2I(Point.X+x, Point.Y-y));
				returnPoint.Add(new Vector2I(Point.X-x, Point.Y+y));
				returnPoint.Add(new Vector2I(Point.X-x, Point.Y-y));
			}
			if(!IsSolid && Math.Abs(Math.Pow(Math.Pow(x,2)+Math.Pow(y,2),0.5) - R) < Accuracy)
			{
				returnPoint.Add(new Vector2I(Point.X+x, Point.Y+y));
				returnPoint.Add(new Vector2I(Point.X+x, Point.Y-y));
				returnPoint.Add(new Vector2I(Point.X-x, Point.Y+y));
				returnPoint.Add(new Vector2I(Point.X-x, Point.Y-y));
			}
		}
	}
	return returnPoint;
}


// --------- 矩形函数 ------------
// 函数重载，有两种用法：
// 1、传入：左上角座标、宽、高  得到一个实心矩形
// 2、传入：左上角座标、宽、高、边框宽度   得到一个空心矩形
// 【注意】面积不宜太大
List<Vector2I> DrawRectangle(Vector2I Point, int Width, int Height)
{
	List<Vector2I> returnPoint = new List<Vector2I>();
	for(int x = Point.X; x < Point.X + Width; x++)
	{
		for(int y = Point.Y; y < Point.Y + Height; y ++)
		{
			returnPoint.Add(new Vector2I(x, y));//生成整个实心矩形
		}
	}
	return returnPoint;
}
List<Vector2I> DrawRectangle(Vector2I Point, int Width, int Height, int Thickness)
{
	List<Vector2I> returnPoint = DrawRectangle(Point, Width, Height);
	//生成矩形2个对角点
	Vector2I a_point = Point; //左上
	Vector2I d_point = new Vector2I(Point.X+Width, Point.Y+Height); //右下
	List<Vector2I> rightPoint = new List<Vector2I>();
	for(int i = 0; i < returnPoint.Count; i ++)
	{
		if(returnPoint[i].X < a_point.X + Thickness || returnPoint[i].X >= d_point.X - Thickness)
		{
			rightPoint.Add(returnPoint[i]);
		}
		if(returnPoint[i].Y < a_point.Y + Thickness || returnPoint[i].Y >= d_point.Y - Thickness)
		{
			rightPoint.Add(returnPoint[i]);
		}
	}
	return rightPoint;
}

// --------- 等腰三角形函数 ----------
// 函数重载，有两种用法：
// 1、传入： 顶点座标，底边长，腰高  得到一个实心等腰三角形
// 2、传入： 顶点座标，底边长，腰高，边框宽度，是否需要底边   如果需要底边得到一个空心等腰三角形，不需要底边则会获得一个箭头
// 【注意】空心型边框不宜过大
List<Vector2I> DrawTriangle(Vector2I Point, int Width, int Height)
{
	List<Vector2I> returnPoint = new List<Vector2I>();
	for(int h = 0; h < Height; h++)
	{
		for(int l = 0; l < 0.5*h*Width/Height; l ++)
		{
			returnPoint.Add(new Vector2I(Point.X+l, Point.Y+h));
			returnPoint.Add(new Vector2I(Point.X-l, Point.Y+h));
		}
	}
	return returnPoint;
}
List<Vector2I> DrawTriangle(Vector2I Point, int Width, int Height, int Thickness, bool NeedBase)
{
	List<Vector2I> returnPoint = new List<Vector2I>();
	for(int h = 0; h < Height; h++)
	{
		for(int i = 0; i < Thickness; i ++)
		{
			returnPoint.Add(new Vector2I(Point.X+(int)Math.Round(0.5*h*Width/Height, 0)-i, Point.Y+h));
			returnPoint.Add(new Vector2I(Point.X-(int)Math.Round(0.5*h*Width/Height, 0)+i, Point.Y+h));
			if(NeedBase)
			{
				for(int x = 0; x < Width; x ++)
				{
					returnPoint.Add(new Vector2I(Point.X - (int)Math.Round(0.5*Width, 0) + x, Point.Y+Height-i));
				}
			}
		}
	}
	return returnPoint;
}

// --------------- 多项式图像 -------------------
// 传入：string多项式、x取值范围，函数图
// 多项式格式应该是 指数、系数
List<Vector2I> DrawPolynomial(double[][] Function, int start_x, int end_x)
{
	List<Vector2I> returnPoint = new List<Vector2I>();
	for(int x = start_x; x <= end_x; x ++)
	{
		int this_y = 0;
		for(int n = 0; n < Function.Length; n ++)
		{
			this_y += (int)Math.Round(Math.Pow(x, Function[n][0])*Function[n][1]);
		}
		returnPoint.Add(new Vector2I(x, this_y));
	}
	return returnPoint;
}

// -------- string字符串转像素字符 ---------
public List<Vector2I> DrawTextLine(string Input_Text, int X1, int Y1)  //这是一个很不耐烦的函数，碰到\n就会退出
{
	int XShift = X1;
	int YShift = Y1;

	List<Vector2I> L_DotList = new List<Vector2I>();

	for (int iText = 0; (iText < Input_Text.Length); iText++)
	{
		char CharStep = Input_Text[iText];
		switch (CharStep)
		{
		case ' ':
			XShift += 4;
			break;
		case '\n':
			XShift = 0;
			YShift += 6;
			continue;   //遇到\n直接退出
		default:
			short CharBitmap = T_GetBitmap(CharStep);
			int i = 14;
			do
			{
				if ((CharBitmap & 1) == 1)
				{
					L_DotList.Add(new Vector2I(XShift + i % 3, YShift - 4 + i / 3));
				}
				CharBitmap >>= 1;
				i--;
			}
			while (CharBitmap > 0);
			XShift += 4;
			//Thanks to Blargmode, he taught me how to use it
			break;
		}
	}
	return L_DotList;
}

public short T_GetBitmap(char Input_Char)  //非静态
{
	if (Input_Char > 127)
	{
		return CError[1];   //缺少错误提示&错误处理
	}
	return CChar[Input_Char - iCharOffset];
}

public const short iCharOffset = 0x21;

public short[] CChar =     //k社使我被迫删了const
{9346,23040,24445,15602,17057,10923,9216,5265,17556,21824,1488,20,448,2,672,31599,11415,25255,29326,23497,31118,10666,29330,10922,10954,1040,1044,5393,3640,17492,25218,15203,11245,27566,14627,27502,31143,31140,14827,23533,29847,12906,23469,18727,24557,27501,11114,27556,11131,27565,14478,29842,23403,23378,23549,23213,23186,29351,13459,2184,25750,10752,7,17408,239,18862,227,4843,1395,14756,1886,18861,8595,4302,18805,25745,509,429,170,1396,1369,228,1934,18851,363,362,383,341,2766,3671,5521,9234,17620,1920};

public short[] CError =    //k社使我被迫删了const
{
	4095,	//0全色填充
	2047,	//1非ASCII识别错误（1号NoBitmap错误）
	3071,	//2
	3583,	//3
	3839,	//4
	3967,	//5
	4031,	//6
	4063,	//7
	4079,	//8
	4087,	//9
	4091,	//10
	4093,	//11
	4094	//12
};

// ============================================== 颜色类 =========================================
//输入RGB值返回颜色char函数，允许输入超范围
public char GetColorFromRGB(double R, double G, double B)    //Large RGB Convertor(0 - 225)
{
	R /= 32;
	G /= 32;
	B /= 32;
	byte CR = (byte)(R > 255 ? 255 : (R < 0 ? 0 : R));
	byte CG = (byte)(G > 255 ? 255 : (G < 0 ? 0 : G));
	byte CB = (byte)(B > 255 ? 255 : (B < 0 ? 0 : B));
	return (char)(0xE100 + (CR << 6) + (CG << 3) + CB);
}